/*
 * $Author: isan.wong@gmail.com $
 * $Date: 2011-07-29 01:03:06 +0000 (Fri, 29 Jul 2011) $
 * $Rev: 13 $
 * $URL: http://capitalbio-smd.googlecode.com/svn/src/com/capitalbio/smd/base/Clustal.java $
 */

/**
 * ClustalWAlign.java
 * 
 * @author Dickson S. Guedes (guedes@unisul.br)
 * @author Israel S. de Medeiros (israelbrz@gmail.com)
 * @version 1.0	
 * @serialData 20060120 
 * 
 * Copyright (c) 2006.
 * 
 */

package com.capitalbio.smd.base;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.biojava.bio.BioException;
import org.biojava.bio.seq.Sequence;
import org.biojava.bio.seq.db.HashSequenceDB;
import org.biojava.bio.seq.db.SequenceDB;
import org.biojavax.bio.seq.RichSequence.IOTools;
import org.biojavax.bio.seq.RichSequenceIterator;
import org.biojava.utils.ChangeVetoException;

/**
 * Align sequences using ClustalW application
 * For attention: Only for DNA now (Optimized for DNA alignment)
 * @author Wang Lei
 *
 */
public class Clustal {
	protected static Log log = LogFactory.getLog(Clustal.class);
	
	private String clustalwExePath;

	private File INFILE;
	private File OUTFILE;
	private File DNDFILE;
	
	/**
	 * Default ClustalW parameters
	 */
	private String PWMATRIX;
	private float PWGAPOPEN;
	private float PWGAPEXT;
	
	private String MATRIX;
	private float GAPOPEN;
	private float GAPEXT;
	private boolean NEGATIVE;

	private SequenceDB sequenceDB;
	private String guideTree;

	/**
	 * 创建一个ClustalWAlign实例
	 * 实例名称默认为"na"
	 * @throws Exception 
	 */
	public Clustal() throws Exception {
		this("na");
	}
	
	/**
	 * 创建一个ClustalWAlign实例
	 * @param id 实例名称
	 * @throws Exception 
	 */
	public Clustal(String id) throws Exception {
		
		try {
			String clustalBin = Util.getProperties().getProperty("smd.alignment.clustal.bin");
			log.debug(clustalBin);
			
			URI clustalwExeURI = Util.getResourceURL(clustalBin).toURI();
			File clustalwExeFile = new File(clustalwExeURI);
			clustalwExePath = clustalwExeFile.getAbsolutePath();
			
			if (!clustalwExeFile.canExecute()) {
				throw new Exception("Check your CLUSTAL W path and permissions.");
			}
		} catch (URISyntaxException e) {
			throw e;
		} catch (Exception e) {
			throw e;
		}
		
		//将id中的所有"."去掉,否则会影响dnd文件的创建
		id = id.replaceAll("\\.", "_");
		try {
			INFILE = File.createTempFile("svd_" + id + "_", "");
			INFILE.deleteOnExit();
			
			log.debug("ClustalW alignment file: " + INFILE.getName());
			
			OUTFILE = new File(INFILE.getAbsolutePath() + ".out");
			OUTFILE.deleteOnExit();
			
			DNDFILE = new File(INFILE.getAbsolutePath() + ".dnd");
			DNDFILE.deleteOnExit();
		
		} catch (IOException e) {
			log.error(e);
		}
		
		//initialize parameters
		PWMATRIX = Util.getMatrixProperty("smd.alignment.clustal.pwmatrix");
		MATRIX = Util.getMatrixProperty("smd.alignment.clustal.matrix");
		PWGAPOPEN = Util.getFloatProperty("smd.alignment.clustal.pwgapopen");
		PWGAPEXT = Util.getFloatProperty("smd.alignment.clustal.pwgapext");
		GAPOPEN = Util.getFloatProperty("smd.alignment.clustal.gapopen");
		GAPEXT = Util.getFloatProperty("smd.alignment.clustal.gapext");
		NEGATIVE = Util.getBooleanProperty("smd.alignment.clustal.negative");
		sequenceDB = new HashSequenceDB();
	}

	public int doMultAlign() {
		int exitVal = 999;

		try {
			IOTools.writeFasta(new FileOutputStream(INFILE), sequenceDB.sequenceIterator(), null);

			Runtime rt = Runtime.getRuntime();
			String[] strComando = {
				clustalwExePath,
				"-INFILE=" + INFILE.getAbsolutePath(),
				"-OUTFILE=" + OUTFILE.getAbsolutePath(),
				"-PWDNAMATRIX=" + PWMATRIX,
				"-PWGAPOPEN=" + PWGAPOPEN,
				"-PWGAPEXT=" + PWGAPEXT,
				"-MATRIX=" + MATRIX,
				"-GAPOPEN=" + GAPOPEN,
				"-GAPEXT=" + GAPEXT,
				"-OUTPUT=FASTA",
				"-TYPE=DNA",
				NEGATIVE ? "-NEGATIVE" : "",
				"-ALIGN"
			};
			Process proc = rt.exec(strComando);

			InputStream stdin = proc.getInputStream();
			BufferedReader br = new BufferedReader(new InputStreamReader(stdin));

			while ((br.readLine()) != null) {
				
			}

			exitVal = proc.waitFor();
			if (exitVal == 0) {
				RichSequenceIterator sequenceIterator = IOTools.readFasta(
						new BufferedReader(new FileReader(OUTFILE)),
						Util.getDNATokenization(), null);
				while (sequenceIterator.hasNext()) {
					sequenceDB.addSequence(sequenceIterator.nextSequence());
				}
				guideTree = Util.fileToString(DNDFILE);
			}
			
			Runtime rt1 = Runtime.getRuntime();
			String[] strComando1 = {
				clustalwExePath,
				"-INFILE=" + INFILE.getAbsolutePath(),
				"-OUTFILE=" + INFILE.getAbsolutePath() + "_aln.aln",
				"-PWDNAMATRIX=" + PWMATRIX,
				"-PWGAPOPEN=" + PWGAPOPEN,
				"-PWGAPEXT=" + PWGAPEXT,
				"-MATRIX=" + MATRIX,
				"-GAPOPEN=" + GAPOPEN,
				"-GAPEXT=" + GAPEXT,
				"-OUTPUT=CLUSTAL",
				"-TYPE=DNA",
				NEGATIVE ? "-NEGATIVE" : "",
				"-ALIGN"
			};
			Process proc1 = rt1.exec(strComando1);
			InputStream stdin1 = proc1.getInputStream();
			BufferedReader br1 = new BufferedReader(new InputStreamReader(stdin1));
			while ((br1.readLine()) != null) {
			}
			
		} catch (Throwable t) {
			log.error(t);
		}
		return (exitVal);
	}

	public SequenceDB getSequenceDB() {
		return sequenceDB;
	}

	/**
	 * Get guide tree
	 * @return guide tree string
	 */
	public String getGuideTree() {
		return guideTree;
	}

	/**
	 * Get multiple alignment matrix
	 * @return multiple alignment matrix
	 */
	public String getMATRIX() {
		return MATRIX;
	}

	/**
	 * Set multiple alignment matrix
	 * @param MATRIX multiple alignment matrix (name or matrix file path)
	 */
	public void setMATRIX(String MATRIX) {
		this.MATRIX = MATRIX;
	}

	public float getGAPOPEN() {
		return GAPOPEN;
	}

	/**
	 * Set gap open penalty
	 * @param GAPOPEN gap open penalty
	 */
	public void setGAPOPEN(float GAPOPEN) {
		this.GAPOPEN = GAPOPEN;
	}

	public float getGAPEXT() {
		return GAPEXT;
	}

	/**
	 * Set gap extension penalty
	 * @param GAPEXT gap extension penalty
	 */
	public void setGAPEXT(float GAPEXT) {
		this.GAPEXT = GAPEXT;
	}

	public String getPWMATRIX() {
		return PWMATRIX;
	}

	/**
	 * Set pairwise alignment matrix
	 * @param PWMATRIX pairwise alignment matrix (name or matrix file path)
	 */
	public void setPWMATRIX(String PWMATRIX) {
		this.PWMATRIX = PWMATRIX;
	}

	public float getPWGAPOPEN() {
		return PWGAPOPEN;
	}

	public void setPWGAPOPEN(float PWGAPOPEN) {
		this.PWGAPOPEN = PWGAPOPEN;
	}

	public float getPWGAPEXT() {
		return PWGAPEXT;
	}

	public void setPWGAPEXT(float PWGAPEXT) {
		this.PWGAPEXT = PWGAPEXT;
	}

	public void addSequence(Sequence seqSequence) throws BioException,
			ChangeVetoException {
		sequenceDB.addSequence(seqSequence);
	}

	public void removeSequence(String idSequence) throws BioException,
			ChangeVetoException {
		sequenceDB.removeSequence(idSequence);
	}
	
	/**
	 * clustal alignment input file
	 * @return file
	 */
	public File getINFILE() {
		return INFILE;
	}

	/**
	 * clustal alignment input file
	 * @param INFILE
	 */
	public void setINFILE(File INFILE) {
		this.INFILE = INFILE;
	}

	/**
	 * clustal alignment output file
	 * @return file
	 */
	public File getOUTFILE() {
		return OUTFILE;
	}

	/**
	 * clustal alignment output file
	 * @param OUTFILE
	 */
	public void setOUTFILE(File OUTFILE) {
		this.OUTFILE = OUTFILE;
	}

	/**
	 * clustal dnd guide tree file
	 * @return file
	 */
	public File getDNDFILE() {
		return DNDFILE;
	}

	/**
	 * clustal dnd guide tree file
	 * @param DNDFILE
	 */
	public void setDNDFILE(File DNDFILE) {
		this.DNDFILE = DNDFILE;
	}

	/**
	 * set if negative score
	 * @param NEGATIVE
	 */
	public void setNEGATIVE(boolean NEGATIVE) {
		this.NEGATIVE = NEGATIVE;
	}
}